import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;


public class FileOps {
	
	int codeSize = 2;
	arrayTree theTree;
	binaryOps bop = new binaryOps(4);
	bitFileStreamIn bitStreamIn;
	bitFileStreamOut bitStreamOut;
	FileInputStream fileIn = null;
	FileOutputStream fileOut = null;
	String inputFile, outputFile;
	FileOps(String input, String output) {
		inputFile = input;
		outputFile = output;
        try {
			fileOut = new FileOutputStream(outputFile);
			fileIn = new FileInputStream(inputFile);
		} catch (FileNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		bitStreamIn = new bitFileStreamIn(fileIn);
		bitStreamOut = new bitFileStreamOut(fileOut);
	}
	void writeCompressedCodeTable() {
		/*
		 * 0-3     tableSize
		 * 4-tableSize+3 table
		 * tableSize+4-End file
		 */
		int tableSize = theTree.numLeafs();
		System.out.println("The table size is: " + ((tableSize * 8)/1024) + " kb");
		writeInteger(tableSize); 
		for(int i=0; i < tableSize; i++) {
			int tempVal[] = theTree.getLeaf(i);
			writeInteger(tempVal[0]);
			writeInteger(tempVal[1]);
		}
	}
	void writeCompressedFile() {
		  
		  byte input[] = new byte[codeSize];
	      try {
	    	long fileSize = fileIn.available();
	    	long byteCount = 0;
	    	int intervalCount = 0;
	    	long interval = fileSize / 100;
	    	System.out.println("Writing the compressed file");
	    	setupProgressBar();
			while ((fileIn.read(input)) != -1) {
			  	int tval = bop.integerFromBytes(input);
			  	byteCount += codeSize;
			  	if(byteCount >= (interval * intervalCount)) {
			  		
			  		printProgressBar(intervalCount);
			  		intervalCount++;
			  	}
			  	boolean writeCode[] = bop.booleanFromInteger(theTree.quickGetAfterCodeFromCode(tval));
			  	writeCode = bop.booleanRemoveLeading1(writeCode); //remove leading 1 and write booleans
			  	writeBooleans(writeCode);
			}
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	void compress(arrayTree tree) { theTree = tree; writeCompressed();};
	void writeCompressed() {
		theTree.buildArray();
		writeCompressedCodeTable();
		writeCompressedFile();
	}
	void decompress() { readCompressed(); };
	void readCompressed() {
		long fileSize = 0;
		int tableSize = 0;
		byte input[] = new byte[4];
		try {
			fileIn.read(input);
			fileSize = fileIn.available();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		tableSize = bop.integerFromBytes(input);
		theTree = new arrayTree(null);
		theTree.buildArrayFromCodeTable(readTable(tableSize));
		
    	long byteCount = 0;
    	int intervalCount = 0;
    	long interval = fileSize / 100;
    	System.out.println("Reading Compressed file");
    	setupProgressBar();
		while(!bitStreamIn.eof) {
			boolean[] outval = getDeCompressedCode();
		 	byteCount += (outval.length / 8);
		  	if(byteCount >= (interval * intervalCount)) {
		  		
		  		printProgressBar(intervalCount);
		  		intervalCount++;
		  	}
			try {
				byte bytesout[] = bop.bytesFromBooleans(outval);
				fileOut.write(bytesout[2]);
				fileOut.write(bytesout[3]);
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
	boolean[] getDeCompressedCode() {
		boolean readval[] = new boolean[32];
		int rPos = 0;
		try {
			readval[0] = true; //add beginning 1;
			readval[1] = bitStreamIn.read();
			rPos += 2;
			while(!theTree.afterCodeIsGood(bop.truncateBooleanFromFront(readval, rPos))) {
				readval[rPos] = bitStreamIn.read();
				rPos++;
			}
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return theTree.getBooleanCodeFromAfterCode(bop.truncateBooleanFromFront(readval, rPos));
	}
	int[][] readTable(int tableSize) {
		int ret[][] = new int[tableSize][2];
		for(int i=0; i < tableSize; i++) {
			int code = readInteger();
			int afterCode = readInteger();
			ret[i][0] = code;
			ret[i][1] = afterCode;
		}
		return ret;
	}
	int readInteger() {
		int retval =0;
		byte input[] = new byte[4];
		try {
			fileIn.read(input);
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		retval = bop.integerFromBytes(input);
		return retval;
	}
	void writeInteger(int in) {
		boolean val[] = bop.booleanFromInteger(in);
		boolean writeval[] = new boolean[32 - val.length];
		for(int i=0; i < writeval.length; i++) writeval[i] = false;
		writeBooleans(writeval);
		writeBooleans(val);
	}
	void writeBooleans(boolean[] in) {
		for(int i=0; i < in.length; i++) {
			try {
				bitStreamOut.write(in[i]);
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
	void setupProgressBar() {
		System.out.println("0% <~                                                   ~> 100%");
		System.out.print("0% <~");
	}
	void printProgressBar(int progress) { //0-100
		if(progress % 2 == 0) System.out.print("~");
		if(progress == 100) System.out.println("~~> 100%");
	}
}
